﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using Xceed.Silverlight.Data;

namespace Xceed.Silverlight.DataGrid
{
  internal sealed class DataGridViewModel : ViewModelBase
  {
    #region Constructor

    internal DataGridViewModel()
    {
      m_validationErrors = new ValidationErrorsObservableCollection<ValidationErrorBase>();
      m_readOnlyValidationErrors = new ReadOnlyObservableCollection<ValidationErrorBase>( m_validationErrors );
    }

    #endregion

    #region ValidationErrors Property

    internal ReadOnlyObservableCollection<ValidationErrorBase> ValidationErrors
    {
      get
      {
        return m_readOnlyValidationErrors;
      }
    }

    private readonly ReadOnlyObservableCollection<ValidationErrorBase> m_readOnlyValidationErrors;
    private readonly ValidationErrorsObservableCollection<ValidationErrorBase> m_validationErrors;

    #endregion

    #region HasValidationErrors Property

    internal bool HasValidationErrors
    {
      get
      {
        return ( this.ValidationErrors.Count > 0 );
      }
    }

    #endregion

    #region HasRestrictiveValidationErrors Property

    internal bool HasRestrictiveValidationErrors
    {
      get
      {
        return ( this.ValidationErrors.Any( ( item ) => item.IsRestrictive ) );
      }
    }

    #endregion

    #region EditedRows Property

    internal IEnumerable<EditableRowViewModel> EditedRows
    {
      get
      {
        return ( from row in m_editedRows.Values
                 where row.IsBeingEdited
                 select row );
      }
    }

    private void RaiseEditedRowsChanged()
    {
      this.RaisePropertyChanged( () => this.EditedRows );
    }

    #endregion

    protected override void OnInitialize()
    {
      base.OnInitialize();

      m_validationErrors.CollectionChanged += new NotifyCollectionChangedEventHandler( this.OnValidationErrorsCollectionChanged );
    }

    protected override void OnClear()
    {
      base.OnClear();

      m_validationErrors.Clear();
      m_validationErrors.CollectionChanged -= new NotifyCollectionChangedEventHandler( this.OnValidationErrorsCollectionChanged );

      m_validationErrorsMapping.Clear();

      var editedRows = m_editedRows.Values.ToList();
      m_editedRows.Clear();
      foreach( var row in editedRows )
      {
        if( !m_rows.ContainsKey( row.DataPath ) )
        {
          this.ClearRow( row );
        }
      }

      var rows = m_rows.Values.ToList();
      m_rows.Clear();
      foreach( var row in rows )
      {
        this.ClearRow( row );
      }
    }

    internal EditableRowViewModel GetRow( DataPath dataPath, object dataItem, bool isNewItem )
    {
      EditableRowViewModel row;
      if( m_editedRows.TryGetValue( dataPath, out row ) )
        return row;

      if( m_rows.TryGetValue( dataPath, out row ) )
        return row;

      row = this.CreateRow( dataPath, dataItem, isNewItem );
      if( row == null )
        throw new DataGridInternalException();

      m_rows.Add( dataPath, row );
      this.PrepareRow( row );

      return row;
    }

    internal void ReleaseRow( DataPath dataPath )
    {
      EditableRowViewModel row;
      if( !m_rows.TryGetValue( dataPath, out row ) )
        return;

      m_rows.Remove( dataPath );

      this.KeepClearRow( row );
    }

    private EditableRowViewModel CreateRow( DataPath dataPath, object dataItem, bool isNewItem )
    {
      return new EditableRowViewModel( dataPath, dataItem, isNewItem );
    }

    private void PrepareRow( EditableRowViewModel row )
    {
      this.SubscribeValidationErrors( row );

      row.PropertyChanged += new PropertyChangedEventHandler( this.OnRowPropertyChanged );
      row.Releasing += new EventHandler( this.OnRowReleasing );
      row.Initialize();
    }

    private void ClearRow( EditableRowViewModel row )
    {
      this.UnsubscribeValidationErrors( row );

      row.PropertyChanged -= new PropertyChangedEventHandler( this.OnRowPropertyChanged );
      row.Releasing -= new EventHandler( this.OnRowReleasing );
      row.Clear();
    }

    private void SubscribeValidationErrors( EditableRowViewModel row )
    {
      INotifyCollectionChanged rowValidationErrors = row.ValidationErrors;
      INotifyCollectionChanged cellsValidationErrors = row.CellsValidationErrors;

      m_validationErrorsMapping.Add( rowValidationErrors, row );
      m_validationErrorsMapping.Add( cellsValidationErrors, row );

      rowValidationErrors.CollectionChanged += new NotifyCollectionChangedEventHandler( this.OnRowValidationErrorsCollectionChanged );
      cellsValidationErrors.CollectionChanged += new NotifyCollectionChangedEventHandler( this.OnCellsValidationErrorsCollectionChanged );
    }

    private void UnsubscribeValidationErrors( EditableRowViewModel row )
    {
      INotifyCollectionChanged rowValidationErrors = row.ValidationErrors;
      INotifyCollectionChanged cellsValidationErrors = row.CellsValidationErrors;

      m_validationErrorsMapping.Remove( rowValidationErrors );
      m_validationErrorsMapping.Remove( cellsValidationErrors );

      rowValidationErrors.CollectionChanged -= new NotifyCollectionChangedEventHandler( this.OnRowValidationErrorsCollectionChanged );
      cellsValidationErrors.CollectionChanged -= new NotifyCollectionChangedEventHandler( this.OnCellsValidationErrorsCollectionChanged );

      this.RemoveValidationErrors( row );
    }

    private void KeepClearRow( EditableRowViewModel row )
    {
      var key = row.DataPath;

      if( row.KeepAlive )
      {
        if( m_editedRows.ContainsKey( key ) )
          return;

        m_editedRows.Add( key, row );
        this.RaiseEditedRowsChanged();
      }
      else
      {
        if( m_editedRows.Remove( key ) )
        {
          this.RaisePropertyChanged( () => this.EditedRows );
        }

        if( !m_rows.ContainsKey( key ) )
        {
          this.ClearRow( row );
        }
      }
    }

    private void RemoveValidationErrors( EditableRowViewModel row )
    {
      if( row == null )
        return;

      using( m_validationErrors.Batch() )
      {
        this.RemoveRowValidationErrors( row );
        this.RemoveCellsValidationErrors( row );
      }
    }

    private void RemoveRowValidationErrors( EditableRowViewModel row )
    {
      using( m_validationErrors.Batch() )
      {
        var dataPath = row.DataPath;
        for( int i = m_validationErrors.Count - 1; i >= 0; i-- )
        {
          RowValidationError validationError = m_validationErrors[ i ] as RowValidationError;
          if( validationError == null )
            continue;

          if( object.Equals( validationError.DataPath, dataPath ) )
          {
            m_validationErrors.RemoveAt( i );
          }
        }
      }
    }

    private void RemoveCellsValidationErrors( EditableRowViewModel row )
    {
      using( m_validationErrors.Batch() )
      {
        var dataPath = row.DataPath;
        for( int i = m_validationErrors.Count - 1; i >= 0; i-- )
        {
          CellValidationError validationError = m_validationErrors[ i ] as CellValidationError;
          if( validationError == null )
            continue;

          if( object.Equals( validationError.DataPath, dataPath ) )
          {
            m_validationErrors.RemoveAt( i );
          }
        }
      }
    }

    private void UpdateRowValidationErrors( INotifyCollectionChanged collection )
    {
      EditableRowViewModel row;
      if( !m_validationErrorsMapping.TryGetValue( collection, out row ) )
        return;

      using( m_validationErrors.Batch() )
      {
        this.RemoveRowValidationErrors( row );

        foreach( var error in row.ValidationErrors )
        {
          m_validationErrors.Add( error );
        }
      }
    }

    private void UpdateCellsValidationErrors( INotifyCollectionChanged collection )
    {
      EditableRowViewModel row;
      if( !m_validationErrorsMapping.TryGetValue( collection, out row ) )
        return;

      using( m_validationErrors.Batch() )
      {
        this.RemoveCellsValidationErrors( row );

        foreach( var error in row.CellsValidationErrors )
        {
          m_validationErrors.Add( error );
        }
      }
    }

    private void OnRowPropertyChanged( object sender, PropertyChangedEventArgs e )
    {
      EditableRowViewModel row = ( EditableRowViewModel )sender;

      if( e.IsPropertyChanged( ( EditableRowViewModel item ) => row.KeepAlive ) )
      {
        this.KeepClearRow( row );
      }
    }

    private void OnRowReleasing( object sender, EventArgs e )
    {
      EditableRowViewModel row = ( EditableRowViewModel )sender;
      EditableRowViewModel targetRow;
      var key = row.DataPath;

      if( !m_rows.TryGetValue( key, out targetRow ) )
      {
        if( !m_editedRows.TryGetValue( key, out targetRow ) )
        {
          targetRow = null;
        }
      }

      if( targetRow == row )
      {
        this.ReleaseRow( key );
      }
      else
      {
        this.ClearRow( row );
      }
    }

    private void OnValidationErrorsCollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
    {
      this.RaisePropertyChanged( () => this.HasValidationErrors );
      this.RaisePropertyChanged( () => this.HasRestrictiveValidationErrors );
    }

    private void OnRowValidationErrorsCollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
    {
      this.UpdateRowValidationErrors( ( INotifyCollectionChanged )sender );
    }

    private void OnCellsValidationErrorsCollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
    {
      this.UpdateCellsValidationErrors( ( INotifyCollectionChanged )sender );
    }

    #region Private Fields

    private readonly Dictionary<DataPath, EditableRowViewModel> m_rows = new Dictionary<DataPath, EditableRowViewModel>();
    private readonly Dictionary<DataPath, EditableRowViewModel> m_editedRows = new Dictionary<DataPath, EditableRowViewModel>();
    private readonly Dictionary<INotifyCollectionChanged, EditableRowViewModel> m_validationErrorsMapping = new Dictionary<INotifyCollectionChanged, EditableRowViewModel>();

    #endregion
  }
}
